package com.vivo.ic.dm;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.http.AndroidHttpClient;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.PowerManager;
import android.text.TextUtils;

import com.vivo.ic.VLog;
import com.vivo.ic.dm.DownloadInfo.NetworkState;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;

/**
 * @author wangzhenyu 子下载线程管理器
 */
class ChildDownloadManager {
    private static final String TAG = Constants.PRE_TAG + "ChildDownloadManager";

    private final Context mContext;
    private DownloadManager mManager;
    private final DownloadInfo mDownloadInfo;
    private ContentResolver mResolver;
    private HandlerThread mSyncThread;
    private Handler mSyncHandler;
    /**
     * 多线程下载时使用几个线程。
     */
    int mCoreSize;
    /**
     * 多线程下载时用到的线程集合
     */
    private final ChildDownloadThread[] mThreads;
    private ChildDownloadInfo[] mChildInfos;
    private final CountDownLatch mCountDownLatch;
    /**
     * 区别于DownloadInfo中的url，这里面是真正的下载地址。
     */
    private String mTempUrl;
    /**
     * 下载过程中出现的异常，任何一个子线程出现崩溃，就停掉所有线程。
     */
    private Exception mException;
    private ArrayList<String> mBlockNameList;
    private int mReportNum = 0;
    private int mFailedNum = 0; // 下载失败的子线程数

    private static final int SERVER_RESPONSE_SUCCESS = 200;

    protected static final int DOWNLOAD_PROGRESS_MESSAGE = 0;
    /**
     * 表示子线程结束
     */
    protected static final int DOWNLOAD_FINISH_MESSAGE = 1;
    /**
     * 表示子下载失败
     */
    protected static final int DOWNLOAD_FAILED_MESSAGE = 2;

    ChildDownloadManager(Context context, DownloadInfo info) {

        mContext = context;
        mResolver = mContext.getContentResolver();
        mManager = DownloadManager.getInstance();
        mDownloadInfo = info;
        mCoreSize = DownloadManager.getConfig().getMultiDownloadCoreSize();
        logDebug("mCoreSize: " + mCoreSize);
        mThreads = new ChildDownloadThread[mCoreSize];
        mChildInfos = new ChildDownloadInfo[mCoreSize];
        mCountDownLatch = new CountDownLatch(mCoreSize);
        mBlockNameList = new ArrayList<String>();
        mSyncThread = new HandlerThread("sync_thread");
        mSyncThread.start();
        initSyncHandler();
        logInfo("ChildDownloadManager Constructor OK");
    }

    private void initSyncHandler() {
        mSyncHandler = new Handler(mSyncThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                ChildDownloadInfo childInfo = (ChildDownloadInfo) msg.obj;
                switch (msg.what) {
                case DOWNLOAD_PROGRESS_MESSAGE:
                    if (Downloads.Impl.isStatusCompleted(mDownloadInfo.mStatus)) {
                        logDebug(childInfo.mTid, "mSyncHandler PROGRESS StatusCompleted ");
                        return;
                    }
                    mReportNum++;
                    logDebug(childInfo.mTid, "mSyncHandler PROGRESS childInfo " + childInfo);
                    logDebug("mSyncHandler PROGRESS mDownloadInfo: " + mDownloadInfo);
                    if (mReportNum >= mCountDownLatch.getCount()) {
                        getCurrentProgress();
                        int rows = mDownloadInfo.writeToDatabase("DOWNLOAD_PROGRESS_MESSAGE");
                        logDebug("mSyncHandler PROGRESS writeToDatabase rows: " + rows + " mDownloadInfo: " + mDownloadInfo);
                        if (rows > 0) {
                            mManager.notifyDownloadSpeed(mDownloadInfo, mDownloadInfo.mSpeed);
                            mManager.notifyDownloadSize(mDownloadInfo, mDownloadInfo.mCurrentBytes, mDownloadInfo.mTotalBytes);
                        } else {
                            mDownloadInfo.mStatus = Downloads.Impl.STATUS_CANCELED;
                        }
                        mReportNum = 0;
                    }
                    break;
                case DOWNLOAD_FINISH_MESSAGE:
                    logDebug(childInfo.mTid, "mSyncHandler FINISH mDownloadInfo " + mDownloadInfo);
                    mCountDownLatch.countDown();
                    break;
                case DOWNLOAD_FAILED_MESSAGE:
                    // 只要有一个子线程下载失败，则停止所有的子线程
                    mFailedNum++;
                    mDownloadInfo.mStatus = childInfo.mStatus;
                    mDownloadInfo.mSpeed = 0;
                    if (mDownloadInfo.mStatus == Downloads.Impl.STATUS_CANCELED) {
                        mDownloadInfo.mCurrentBytes = 0;
                    } else {
                        mDownloadInfo.writeToDatabase("DOWNLOAD_FAILED_MESSAGE");
                    }
                    mManager.notifyDownloadSpeed(mDownloadInfo, mDownloadInfo.mSpeed);
                    mManager.notifyDownloadSize(mDownloadInfo, mDownloadInfo.mCurrentBytes, mDownloadInfo.mTotalBytes);
                    setException(childInfo.mException);
                    logDebug(childInfo.mTid, "mSyncHandler FAILED mFailedNum " + mFailedNum + " mStatus " + mDownloadInfo.mStatus + " exception: "
                            + childInfo.mException.getMessage());
                    break;
                default:
                    break;
                }
            }
        };
    }


    void startDownload() {
        logDebug("startDownload() mDownloadInfo: " + mDownloadInfo);
        if (DownloadInfo.queryDownloadStatus(mResolver, mDownloadInfo.mId) == Downloads.Impl.STATUS_SUCCESS) {
            logDebug("DownloadThread runInternal, " + mDownloadInfo.mId + " already finished; skipping");
            return;
        }

        int currentBytes = DownloadInfo.queryDownloadColumInt(mResolver, mDownloadInfo.mId,
                Downloads.Column.CURRENT_BYTES);
        logDebug("currentBytes from db " + currentBytes);
        if (currentBytes > 0) {
            mDownloadInfo.mResume = true;
        }
        PowerManager.WakeLock wakeLock = null;
        final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        try {
//            checkConnectivity();

            wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
            wakeLock.acquire();
            processResponseHeaders(mDownloadInfo);
            startChildDownload(mDownloadInfo.mTotalBytes);

            // 几个线程进行下载就有几把锁，必须等锁全部解除才会最终下载完。
            logInfo("mCountDownLatch.await() begin ......");
            mCountDownLatch.await();
            logInfo("mCountDownLatch.await() finish !!!!!!");

            if (mCountDownLatch.getCount() == 0) {
                if (isAllChildComplete()) {
                    if (mergeBlocksToFile(mBlockNameList, mDownloadInfo.mFileName)) {
                        handleDownloadSuccess();
                        logInfo("Download Successfully");
                    } else {
                        throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR, "Failed to merge files");
                    }

                }
            }

        } catch (StopRequestException exception) {
            setException(exception);
        } catch (InterruptedException e) {
            logError("get a InterruptedException", e);
            setException(e);
        } finally {
            handleDownloadFinally();
            if (wakeLock != null) {
                wakeLock.release();
            }

            if (Downloads.Impl.isStatusCompleted(mDownloadInfo.mStatus)) {
                clearTmpFile(mBlockNameList, 2000);
            }
            mSyncThread.getLooper().quit();
            logDebug("ChildDownloadManager is over, mDownloadInfo.mStatus: " + mDownloadInfo.mStatus);
        }
    }

    /**
     * 分发下载到子线程
     *
     * @param totalSize 下载文件大小
     */
    private void startChildDownload(long totalSize) throws StopRequestException {
        logDebug("startChildDownload totalSize " + totalSize);

        try {

            if (totalSize <= 0) {
                logDebug("invalid file total size");
                throw new StopRequestException(Downloads.Impl.STATUS_UNKNOWN_ERROR, "invalid file total size");
            }

            String path = DownloadManager.getConfig().getDefaultDownloadPath();
            SpaceHelper.checkSpace(mContext, path, totalSize);

            long blockSize = totalSize / mCoreSize;
            long from = 0;
            long to = blockSize;
            for (int i = 0; i < mCoreSize; i++) {

                ChildDownloadInfo childInfo = new ChildDownloadInfo();
                childInfo.mDownloadId = mDownloadInfo.mId;
                childInfo.mTid = i;
                childInfo.mStartBytes = from;
                childInfo.mEndBytes = i == mCoreSize - 1 ? totalSize - 1 : to;
                childInfo.mUrl = mTempUrl;
                childInfo.mBlockName = mDownloadInfo.mFileName + "_" + childInfo.mTid;
                childInfo.mCurrentBytes = 0;
                childInfo.mTotalBytes = childInfo.mEndBytes - childInfo.mStartBytes + 1;
                childInfo.mResume = mDownloadInfo.mResume;
                getChildCurrentBytes(childInfo);
                mThreads[i] = new ChildDownloadThread(mContext, mDownloadInfo, childInfo, mSyncHandler);
                mChildInfos[i] = childInfo;
                logDebug("childInfo: " + childInfo.toString());
                from = to + 1;
                to = from + blockSize;
                mBlockNameList.add(childInfo.mBlockName);
                mThreads[i].start();
            }
        } catch (StopRequestException e) {
            throw new StopRequestException(e.getFinalStatus(), e.getMessage());

        } catch (Exception e) {
            throw new StopRequestException(Downloads.Impl.STATUS_UNKNOWN_ERROR, "start Child Download error");

        }
    }

    /**
     * 处理HTTP请求的头部信息，并更新数据库
     */
    private void processResponseHeaders(DownloadInfo info) throws StopRequestException {

        logInfo("processResponseHeaders() isResume " + info.mResume);
        mTempUrl = mDownloadInfo.mUri;
        if (info.mResume) { // 断点续传
            info.mTotalBytes = DownloadInfo.queryDownloadColumInt(mResolver, info.mId, Downloads.Column.TOTAL_BYTES);
            logInfo("processResponseHeaders() is resume, info.mTotalBytes " + info.mTotalBytes);
//            return;
        }

        int redirect = 0;
        AndroidHttpClient client = null;
        try {
            client = AndroidHttpClient.newInstance(mDownloadInfo.getUserAgent(), mContext);
            while (redirect++ < Constants.MAX_REDIRECTS) {
                HttpGet request = new HttpGet(mTempUrl);
                HttpResponse response = client.execute(request);
                int statusCode = response.getStatusLine().getStatusCode();

                if (statusCode == SERVER_RESPONSE_SUCCESS) {
                    readResponseHeaders(info, response);

                    logInfo("writing " + info.mUri + " to " + info.mFileName);
                    updateDatabaseFromHeaders(info);
//                    checkConnectivity();
                    return;
                }

                if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307) {
                    Header header = response.getFirstHeader("Location");
                    if (header == null) {
                        throw new StopRequestException(Downloads.Impl.STATUS_HTTP_DATA_ERROR,
                                "got a redirect without location info");
                    }
                    logInfo("statusCode: " + statusCode + " Location :" + header.getValue());

                    String newUri;
                    try {
                        URI redirectUri = new URI(mDownloadInfo.mUri).resolve(new URI(header.getValue()));
                        newUri = redirectUri.toString();
                    } catch (URISyntaxException ex) {
                        logError("Couldn't resolve redirect URI " + header.getValue() + " for " + mDownloadInfo.mUri, ex);
                        throw new StopRequestException(Downloads.Impl.STATUS_HTTP_DATA_ERROR,
                                "Couldn't resolve redirect URI");
                    }
                    mTempUrl = newUri;
                    continue;
                }

                int finalStatus;
                if (Downloads.Impl.isStatusError(statusCode)) {
                    finalStatus = statusCode;
                } else if (statusCode >= 300 && statusCode < 400) {
                    finalStatus = Downloads.Impl.STATUS_UNHANDLED_REDIRECT;
                } else {
                    finalStatus = Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE;
                }

                throw new StopRequestException(finalStatus, "http error " + statusCode);
            }


        } catch (IOException exception) {
            throw new StopRequestException(Downloads.Impl.STATUS_HTTP_DATA_ERROR, "http error " + exception);
        } finally {
            if (client != null) {
                logInfo("client.close()");
                client.close();
            }
        }


        throw new StopRequestException(Downloads.Impl.STATUS_TOO_MANY_REDIRECTS, "too many redirects");

    }

    /**
     * 获取HTTP请求头部的相关信息
     */
    private void readResponseHeaders(DownloadInfo info, HttpResponse response) throws StopRequestException {
        logDebug("readResponseHeaders");
        String headerContentDisposition = "", headerContentLocation = "", headerContentLength = "";
        Header header = response.getFirstHeader("Content-Disposition");
        if (header != null) {
            headerContentDisposition = header.getValue();
        }
        header = response.getFirstHeader("Content-Location");
        if (header != null) {
            headerContentLocation = header.getValue();
        }
        if (info.mMimeType == null) {
            header = response.getFirstHeader("Content-Type");
            if (header != null) {
                info.mMimeType = DMUtil.normalizeMimeType(header.getValue());
            }
        }
        header = response.getFirstHeader("ETag");
        if (header != null) {
            info.mETag = header.getValue();
        }
        String headerTransferEncoding = null;
        header = response.getFirstHeader("Transfer-Encoding");
        if (header != null) {
            headerTransferEncoding = header.getValue();
        }
        if (headerTransferEncoding == null) {
            header = response.getFirstHeader("Content-Length");
            if (header != null) {
                headerContentLength = header.getValue();
                long length = Long.parseLong(headerContentLength);
                if (info.mResume) {
                    if (info.mTotalBytes != length) {
                        throw new StopRequestException(Downloads.Impl.STATUS_HTTP_DATA_ERROR,
                                "resume, server length not equals database total bytes");
                    }
                }
                info.mTotalBytes = length;
            }
        } else {
            logInfo("readResponseHeaders, ignoring content-length because of Transfer-encoding");
            if (headerTransferEncoding.equalsIgnoreCase("chunked")) {
                logInfo("Transfer-encoding is chunked, set mCoreSize to 1");
                mCoreSize = 1;
            }
        }

        boolean noSizeInfo = headerContentLength == null
                && !headerTransferEncoding.equalsIgnoreCase("chunked");
        if (noSizeInfo) {
            throw new StopRequestException(Downloads.Impl.STATUS_HTTP_DATA_ERROR,
                    "can't know size of download, giving up");
        }

        if (TextUtils.isEmpty(info.mFileName)) {
            try {
                info.mFileName = Helpers.generateSaveFile(mContext, mDownloadInfo.mUri, mDownloadInfo.mHint,
                        headerContentDisposition, headerContentLocation, info.mMimeType);
            } catch (IOException e) {
                throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR, "Failed to generate filename: " + e);
            }
        }

    }

    /**
     * HTTP请求头部的信息更新到数据库
     */
    private void updateDatabaseFromHeaders(DownloadInfo info) {
        logDebug("updateDatabaseFromHeaders(), update mFileName mETag mMimeType and mTotalBytes");
        ContentValues values = new ContentValues();
        values.put(Downloads.Column.DATA, info.mFileName);
        if (info.mETag != null) {
            values.put(Downloads.Column.ETAG, info.mETag);
        }
        if (info.mMimeType != null) {
            values.put(Downloads.Column.MIME_TYPE, info.mMimeType);
        }
        values.put(Downloads.Column.TOTAL_BYTES, info.mTotalBytes);

        mResolver.update(mDownloadInfo.getDownloadsUri(), values, null, null);
    }

    /**
     * 检测网络
     *
     * @throws StopRequestException 异常
     */
    private void checkConnectivity() throws StopRequestException {
        final NetworkState networkUsable = mDownloadInfo.checkCanUseNetwork();

        // NO_CONNECTION or BLOCKED
        if (networkUsable != NetworkState.OK) {
            if (!DownloadManager.getConfig().allowUsingMobileNet() && networkUsable == NetworkState.MOBILE) {
                throw new StopRequestException(Downloads.Impl.STATUS_QUEUED_FOR_WIFI, networkUsable.name());
            }
            throw new StopRequestException(Downloads.Impl.STATUS_WAITING_FOR_NETWORK, networkUsable.name());
        }
    }

    /**
     * @param blockList 子文件块列表
     * @param fileName  最终的文件名，包括路径
     * @return true 成功
     */
    private boolean mergeBlocksToFile(ArrayList<String> blockList, String fileName) {
        boolean success = true;
        try {
            OutputStream out = new FileOutputStream(fileName);
            byte[] buffer = new byte[1024];
            InputStream in;
            int readLen = 0;
            for (int i = 0; i < blockList.size(); i++) {
                String str = mBlockNameList.get(i);
                File file = new File(str);
                if (file.exists()) {
                    in = new FileInputStream(file);
                    while ((readLen = in.read(buffer)) != -1) {
                        out.write(buffer, 0, readLen);
                    }
                    out.flush();
                    in.close();
                }
            }
            out.close();
            logDebug("merge file successfully");
            success = true;
        } catch (Exception e) {
            logError("merge file failed", e);
            success = false;
        }
        return success;
    }

    /**
     * 下载成功后更新数据库
     */
    private void handleDownloadSuccess() {
        logDebug("handleDownloadSuccess()");
        mDownloadInfo.mStatus = Downloads.Impl.STATUS_SUCCESS;
        mDownloadInfo.mLastMod = System.currentTimeMillis();
        mDownloadInfo.mSpeed = 0;
        mDownloadInfo.writeToDatabase("handleDownloadSuccess()");
        mManager.notifyDownloadSpeed(mDownloadInfo, mDownloadInfo.mSpeed);
        mManager.notifyDownloadSize(mDownloadInfo, 100, 100);
        mManager.notifyDownloadStopped(mDownloadInfo, mDownloadInfo.mStatus);
        clearTmpFile(mBlockNameList, 0);
    }

    /**
     * 设置下载过程中出现的异常，并在handleDownloadStopped中对各种异常进行相应处理。
     *
     * @param exception 异常
     */
    private void setException(Exception exception) {
        logError("setException() exception of ", exception);
        mException = exception;
    }

    private void handleDownloadFinally() {
        if (mException == null) {
            logDebug("handleDownloadFinally() no exception! ");
        } else {
            if (mException instanceof StopRequestException) {
                int finalStatus = ((StopRequestException) mException).getFinalStatus();
                String message = mException.getMessage();
                mDownloadInfo.mStatus = finalStatus;
                logDebug("handleDownloadFinally() StopRequestException finalStatus " + finalStatus + " message "
                        + message);
            } else {
                logDebug("handleDownloadFinally() Other Exception " + mException.getMessage());
            }
        }

        mDownloadInfo.mSpeed = 0;
        mDownloadInfo.writeToDatabase("handleDownloadFinally()");
        mManager.notifyDownloadStopped(mDownloadInfo, mDownloadInfo.mStatus);
        DownloadManager.getInstance().getNotifier().cancelAllNotification((int) mDownloadInfo.mId);
    }

    // 删除临时的子文件
    private void clearTmpFile(final ArrayList<String> blockList, final int delay) {
        logDebug("clearTmpFile delay: " + delay + "ms");
        if (blockList == null || blockList.isEmpty()) {
            return;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(delay);
                    for (int i = 0; i < blockList.size(); i++) {
                        String str = blockList.get(i);
                        if (!TextUtils.isEmpty(str)) {
                            File file = new File(str);
                            if (file.exists()) {
                                file.delete();
                            }
                        }
                    }
                    logDebug("clearTmpFile ok");
                } catch (Exception e) {
                    logError("clearTmpFile fail ", e);

                }

            }
        }).start();
    }

    // 根据子线程的下载进度计算总的下载进度
    private void getCurrentProgress() {
        long current = 0;
        long speed = 0;
        for (int i = 0; i < mChildInfos.length; i++) {
            ChildDownloadInfo childInfo = mChildInfos[i];
            logDebug(childInfo.mTid, "getCurrentProgress(): childInfo:" + childInfo);
            current += childInfo.mCurrentBytes;
            speed += childInfo.mSpeed;
        }
        mDownloadInfo.mSpeed = speed;
        mDownloadInfo.mCurrentBytes = current;
    }

    /**
     * 子下载是否都已完成
     *
     * @return
     */
    private boolean isAllChildComplete() {
        for (int i = 0; i < mChildInfos.length; i++) {
            ChildDownloadInfo child = mChildInfos[i];
            if (child.mCurrentBytes != child.mTotalBytes) {
                logDebug("isAllChildComplete() some child is not complete");
                return false;
            }
        }
        return true;
    }

    /**
     * 获取子文件大小
     */
    private void getChildCurrentBytes(ChildDownloadInfo childInfo) {
        File f = new File(childInfo.mBlockName);
        long fileLength = f.length();
        childInfo.mCurrentBytes = fileLength;
        logInfo(childInfo.mTid, "getChildCurrentBytes() childInfo.mCurrentBytes: " + childInfo.mCurrentBytes);
    }

    private void logDebug(int tid, String msg) {
        VLog.d(TAG, "[" + mDownloadInfo.mId + "](" + tid + ") " + msg);
    }

    private void logDebug(String msg) {
        VLog.d(TAG, "[" + mDownloadInfo.mId + "] " + msg);
    }

    private void logWarning(int tid, String msg) {
        VLog.w(TAG, "[" + mDownloadInfo.mId + "](" + tid + ") " + msg);
    }

    private void logWarning(String msg) {
        VLog.w(TAG, "[" + mDownloadInfo.mId + "] " + msg);
    }

    private void logInfo(int tid, String msg) {
        VLog.i(TAG, "[" + mDownloadInfo.mId + "](" + tid + ") " + msg);
    }

    private void logInfo(String msg) {
        VLog.i(TAG, "[" + mDownloadInfo.mId + "] " + msg);
    }

    private void logError(int tid, String msg, Throwable t) {
        VLog.e(TAG, "[" + mDownloadInfo.mId + "](" + tid + ") " + msg, t);
    }

    private void logError(String msg, Throwable t) {
        VLog.e(TAG, "[" + mDownloadInfo.mId + "] " + msg, t);
    }

}
